package cn.coufran.springboot.starter.auth.config;

import cn.coufran.commons.Result;
import cn.coufran.springboot.starter.auth.AuthUser;
import cn.coufran.springboot.starter.auth.annotation.Public;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

/**
 * 权限拦截器
 * @author Coufran
 * @version 1.0.0
 * @since 1.0.0
 */
public class AuthInterceptor implements HandlerInterceptor {
    /** 权限拦截路径白名单 */
    private Collection<String> whitePaths = new HashSet<>();
    /** 权限用户 */
    private AuthUser authUser;
    /** 自定义拦截规则 */
    private List<AuthAddition> authAdditions;

    /**
     * 构造拦截器
     * @param authUser 权限用户
     */
    public AuthInterceptor(AuthUser authUser) {
        this.authUser = authUser;
    }

    /**
     * 设置权限拦截路径白名单
     * @param whitePaths 权限拦截路径白名单
     */
    public void setWhitePaths(List<String> whitePaths) {
        this.whitePaths = whitePaths;
    }

    /**
     * 添加权限拦截路径白名单
     * @param whitePath 权限拦截路径白名单
     */
    public void addWhitePath(String whitePath) {
        this.whitePaths.add(whitePath);
    }

    /**
     * 移除权限拦截路径白名单
     * @param whitePath 权限拦截路径白名单
     */
    public void removeWhitePath(String whitePath) {
        this.whitePaths.remove(whitePath);
    }

    /**
     * 设置自定义拦截规则
     * @param authAdditions 自定义拦截规则
     */
    public void setAuthAdditions(List<AuthAddition> authAdditions) {
        this.authAdditions = authAdditions;
    }

    /**
     * 拦截请求，做权限校验
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // OPTIONS预检请求，直接放行
        String method = request.getMethod();
        if ("OPTIONS".equals(method)) {
            return true;
        }

        // 白名单接口，直接放行
        if (whitePaths != null && !whitePaths.isEmpty()) {
            String servletPath = request.getServletPath();
            if (whitePaths.contains(servletPath)) {
                return true;
            }
        }

        // Public接口，直接放行
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Public publicAnnotation = handlerMethod.getMethod().getAnnotation(Public.class);
            if (publicAnnotation != null) {
                return true;
            }
            publicAnnotation = handlerMethod.getBeanType().getAnnotation(Public.class);
            if (publicAnnotation != null) {
                return true;
            }
        }

        // 已认证过滤
        if (!authUser.isAuthenticated()) {
            this.writeResponse(response, HttpStatus.UNAUTHORIZED, 40101, "请登录");
            return false;
        }

        // 附加条件过滤
        if (authAdditions != null) {
            for (AuthAddition authAddition : authAdditions) {
                try {
                    authAddition.authIntercept(authUser, request, handler);
                } catch (AuthAddition.AuthException e) {
                    HttpStatus status = e.getHttpStatus();
                    Result result = e.getResult();
                    this.writeResponse(response, status, result);
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 输出到Response对象
     * @param response Response对象
     * @param status HTTP状态
     * @param result 响应结果
     * @throws IOException 发生读写错误
     */
    private void writeResponse(HttpServletResponse response, HttpStatus status, Result result) throws IOException {
        response.setStatus(status.value());
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        String responseBody = JSON.toJSONString(result);
        writer.write(responseBody);
    }

    /**
     * 输出到Response对象
     * @param response Response对象
     * @param status HTTP状态
     * @param code 响应结果编码
     * @param msg 响应结果消息
     * @throws IOException 发生读写错误
     */
    private void writeResponse(HttpServletResponse response, HttpStatus status, int code, String msg) throws IOException {
        this.writeResponse(response, status, Result.error(code, msg));
    }
}
